package com.cloud.seckill.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.cloud.common.utils.R;
import com.cloud.seckill.feign.CouponFeignService;
import com.cloud.seckill.feign.ProductFeignService;
import com.cloud.seckill.to.SkuInfoRedisTo;
import com.cloud.seckill.vo.SeckillSessionWithSkus;
import com.cloud.seckill.vo.SeckillSkuVo;
import com.cloud.seckill.vo.SkuInfoVo;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.redisson.transaction.HashKey;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lisw
 * @create 2021/8/22 17:25
 */
@Service
public class SeckillServiceImpl implements SeckillService {

    @Resource
    private CouponFeignService couponFeignService;
    @Resource
    private ProductFeignService productFeignService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private RedissonClient redissonClient;

    private final String SESSION_CACHE_PREFIX = "seckill:sessions:";
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus";
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";

    @Override
    public void uploadSeckillSkuLatest3Days() {
        // 1、去数据库扫描哪些活动是参加秒杀的
        R r = couponFeignService.getLatest3DaySession();
        if (0 == ((Integer) r.get("code"))) {
            List<SeckillSessionWithSkus> sessionWithSkus = r.getData(new TypeReference<List<SeckillSessionWithSkus>>() {
            });
            // 缓存到redis
            if (sessionWithSkus != null) {
                // 1、缓存活动信息
                saveSessionInfo(sessionWithSkus);
                // 2、缓存活动的关联商品信息
                saveSessionSkuInfo(sessionWithSkus);
            }
        }
    }

    @Override
    public List<SkuInfoRedisTo> queryCurrentSeckill() {
        // 1、 确定当前时间属于哪个场次
        long time = System.currentTimeMillis();
        Set<String> keys = redisTemplate.keys(SESSION_CACHE_PREFIX + "*");
        for (String key : keys) {
            String replace = key.replace(SESSION_CACHE_PREFIX, "");
            String[] startAndEnd = replace.split("_");
            // seckill:sessions:1629770400000_1629777600000 保存的时间是距离1970年的时间
            Long start = Long.valueOf(startAndEnd[0]);
            Long end = Long.valueOf(startAndEnd[1]);
            if (time >= start && time <= end) {
                // 1、获取到当前秒杀的商品场次信息
                List<String> sessionList = redisTemplate.opsForList().range(key, -100, 100);
                // 2、获取这个秒杀场次需要的所有商品信息
                BoundHashOperations<String, String, String> hashOps =
                        redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> skuRedisInfoJson =
                        hashOps.multiGet(sessionList);
                if (null != skuRedisInfoJson && skuRedisInfoJson.size() > 0) {
                    List<SkuInfoRedisTo> skus = skuRedisInfoJson.stream().map(item -> {
                        SkuInfoRedisTo skuInfoRedisTo = JSON.parseObject(item, SkuInfoRedisTo.class);
                        return skuInfoRedisTo;
                    }).collect(Collectors.toList());
                    return skus;
                }
                break;
            }

        }
        return null;
    }

    private void saveSessionInfo(List<SeckillSessionWithSkus> sessionWithSkus) {
        sessionWithSkus.stream().forEach(seckillSessionWithSkus -> {
            long startTime = seckillSessionWithSkus.getStartTime().getTime();
            long endTime = seckillSessionWithSkus.getEndTime().getTime();
            // redis中的缓存key
            String key = SESSION_CACHE_PREFIX + startTime + "_" + endTime;
            // 缓存活动信息
            Boolean hasKey = redisTemplate.hasKey(key);
            if (!hasKey) {
                List<String> skuList = seckillSessionWithSkus.getRelationSkus().stream().
                        map(item -> item.getPromotionSessionId() + "_" + item.getSkuId().toString()).collect(Collectors.toList());
                redisTemplate.opsForList().leftPushAll(key, skuList);
            }
        });
    }

    private void saveSessionSkuInfo(List<SeckillSessionWithSkus> sessionWithSkus) {
        BoundHashOperations<String, Object, Object> hashOperations =
                redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        sessionWithSkus.stream().forEach(sessionWithSku -> {
            sessionWithSku.getRelationSkus().stream().forEach(item -> {
                // 参数格式 : 场次_skuId
                String sessionSkuValue = item.getPromotionSessionId() + "_" + item.getSkuId().toString();
                // 4、商品的随机码？
                String token = UUID.randomUUID().toString().replace("-", "");
                if (!hashOperations.hasKey(sessionSkuValue)) {
                    SkuInfoRedisTo redisTo = new SkuInfoRedisTo();
                    // 缓存商品
                    // 1、sku的基本数据
                    R r = productFeignService.getSkuInfo(item.getSkuId());
                    if (((Integer) r.get("code")) == 0) {
                        SkuInfoVo skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                        });
                        redisTo.setSkuInfo(skuInfo);
                    }

                    // 2、sku的秒杀信息
                    BeanUtils.copyProperties(item, redisTo);

                    // 3、设置当前商品的开始时间和结束时间
                    redisTo.setStartTime(sessionWithSku.getStartTime().getTime());
                    redisTo.setEndTime(sessionWithSku.getEndTime().getTime());

                    // 如果当前场次的商品库存信息已经上架就不需要上架
                    // 5、设置商品的信号量(设置分布式信号量)
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                    // 商品可以秒杀的数量作为该商品的信号量
                    semaphore.trySetPermits(item.getSeckillCount().intValue());
                    redisTo.setRandomCode(token);
                    String redisJson = JSON.toJSONString(redisTo);
                    hashOperations.put(sessionSkuValue, redisJson);
                }

            });
        });
    }
}
